/**
 * @file simple_message_server.c
 * Verteilte Systeme - TCP/IP Programmierübung
 *
 * tcpip server
 *
 * @author Theresa Meiksner - theresa.meiksner@technikum-wien.at
 * @author Ronald Wimmer    - ronald.wimmer@technikum-wien.at
 * @date 2013/12/09
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <netdb.h>
#include <unistd.h>

/* number of backlog = maximum queue length for incoming connection */
#define BACKLOG 10


/**
 * \brief determines the server port by the give command line arguments
 *
 * \param argc - argument count
 * \param argv - argument variables
 *
 */
void getServerPortFromCmdLineArgs(int argc, char *argv[], char** server_port);

/**
 * \brief method for removing process entries to prevent zombie processes
 *
 * called by signal handler
 *
 */
void rmProcesses();

/**
 * \brief prints the usage = which arguments can be used
 *
 */
void showUsage();

/**
 * \brief sets up the servers listening socket with the given server port
 *
 * \param server_port - port for listening socket
 *
 */
void initiateListeningSocket(char *server_port);

/**
 * \brief server loop, where incoming connections are handled,
 *  (where new sockets are created, child processes are spawned etc.)
 *
 */
void startServer();

/**
 * \brief error handling routine
 *
 * \param location - info about the location, where the error happened
 * \param errorMessage - additional information about the specific error
 *
 */
void showErrorMessageAndExit(char* location, const char* errorMessage);

/* name of program, for error logging*/
char *programName = NULL;

/* the listening socket */
int socketFileDescriptor = 0;

int main(int argc, char *argv[]) {

	char *server_port;

	programName = argv[0];

   	getServerPortFromCmdLineArgs(argc, argv, &server_port);

   	initiateListeningSocket(server_port);

	startServer();

	return 0;
}

void getServerPortFromCmdLineArgs(int argc, char *argv[], char** server_port)
{
	int argument;
	unsigned long server_port_num;

	/* retrieve port argument from commandline */
	while ((argument = getopt(argc, argv, "p:")) != -1)
		switch (argument) {
		case 'p':
			*server_port = optarg;
			break;
		case '?':
			showUsage();
			exit(EXIT_SUCCESS);
		default:
			showUsage();
			exit(EXIT_FAILURE);
		}

	/* if no port, we exit */
	if (*server_port == NULL)
	{
		showUsage();
		exit(EXIT_FAILURE);
	}

	/* convert to unsigned short */
	server_port_num = (unsigned long) strtoul(*server_port, NULL, 0);

	/* port should be in a specific range */
	if (server_port_num < 1024 || server_port_num > 65535) {
		showUsage();
		exit(EXIT_FAILURE);
	}
}

void initiateListeningSocket(char *server_port)
{
	struct sigaction signal_action;
	struct addrinfo hints;
	struct addrinfo *socket_address;

	/* passive open */
	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	/* get the address info for the given port and hints*/
	if ((getaddrinfo(NULL, server_port, &hints, &socket_address)) != 0) {
		showErrorMessageAndExit("getaddrinfo()", "error while calling getaddrinfo");
	}

	/* call in sequence: socket - bind - listen */
	if ((socketFileDescriptor = socket(socket_address->ai_family,
			socket_address->ai_socktype, socket_address->ai_protocol)) == -1) {
		showErrorMessageAndExit("socket()", "error while calling socket");
	}

	if (bind(socketFileDescriptor, socket_address->ai_addr, socket_address->ai_addrlen) == -1) {
		showErrorMessageAndExit("bind()", "error while calling bind");
	}

	if ((listen(socketFileDescriptor, BACKLOG)) == -1) {
		showErrorMessageAndExit("listen()", "error while calling listen");
	}

	/* register a signal handler (removeProcessEntries) => is called child process stops */
	memset(&signal_action, 0, sizeof signal_action);
	signal_action.sa_handler = rmProcesses;
	sigemptyset(&signal_action.sa_mask);
	signal_action.sa_flags = SA_RESTART;

	if (sigaction(SIGCHLD, &signal_action, NULL) == -1) {
		showErrorMessageAndExit("sigaction()", "error while calling sigaction");
	}
  	freeaddrinfo(socket_address);
}

void startServer()
{
	struct sockaddr_storage client_socket_address;
	socklen_t client_socket_size;
	int new_socket_fd = 0;

	for(;;) {
		client_socket_size = sizeof(client_socket_address);

		/* accept an incoming connection and create a new socket*/
		new_socket_fd = accept(socketFileDescriptor, (struct sockaddr *) &client_socket_address, &client_socket_size);

		if (new_socket_fd == -1) {
			showErrorMessageAndExit("accept()", "error while creating new socket");
		}

		/* fork a child process */
		if (!fork()) {
			/* we are in the child process, we can close the listening socket*/
			if (close(socketFileDescriptor) == -1)
			{
				showErrorMessageAndExit("close()", "error while calling close on listening socket");
			}
			/* duplicate the socket for the child program */
			if ((dup2(new_socket_fd, STDIN_FILENO)) == -1) {
				showErrorMessageAndExit("dup2()", "error while calling dup2 on new socket and STDIN_FILENO");
			}

			if ((dup2(new_socket_fd, STDOUT_FILENO)) == -1) {
				showErrorMessageAndExit("dup2()", "error while calling dup2 on new socket and STDOUT_FILENO");
			}

			if ((execlp("simple_message_server_logic", "simple_message_server_logic", NULL)) < 0)
			{
				showErrorMessageAndExit("execlp()", "error while calling external program simple_message_server_logic");
			}
			close(new_socket_fd);
			exit(EXIT_SUCCESS);
		}
		else
		{
			/* we are in the parent => we can close the new_socket_fd */
			close(new_socket_fd);
		}
	}

}


void showUsage() {
	fprintf(stderr, "%s:\n", programName);
	fprintf(stderr, "usage: simple_message_server option\n");
	fprintf(stderr, "options:\n");
	fprintf(stderr, "\t-p, --port <port>\n");
	fprintf(stderr, "\t-h, --help\n");
	fprintf(stderr, "TCP Port Range = 1024 - 65535\n");
}

void rmProcesses() {
	while(waitpid(-1, NULL, WNOHANG) > 0);
}

void showErrorMessageAndExit(char* location, const char* errorMessage)
{
	/* No error handling here, because we are already in the error handling routine */
	if (socketFileDescriptor > 0)
		close(socketFileDescriptor);

	fprintf(stderr, "last errno: \n");
	perror(programName);
	fprintf(stderr, "Error occured in - %s: %s\n", location, errorMessage);
	exit(EXIT_FAILURE);
}
